To be able to edit code and run cells, you need to run the notebook yourself. Where would you like to run the notebook?

This notebook takes about 40 seconds to run.

In the cloud (experimental)

Binder is a free, open source service that runs scientific notebooks in the cloud! It will take a while, usually 2-7 minutes to get a session.

On your computer

(Recommended if you want to store your changes.)

  1. Copy the notebook URL:
  2. Run Pluto

    (Also see: How to install Julia and Pluto)

  3. Paste URL in the Open box

Frontmatter

If you are publishing this notebook on the web, you can set the parameters below to provide HTML metadata. This is useful for search engines and social media.

Author 1

Reactive Python

The pyr" macro is just like the py" macro from PyCall.jl, except it is reactive! This means:

  • If you define a Python variable, then other cells that use that python variable are also evaluated.

  • The scope of Python variables is cleaned up automatically.

  • Multiple definitions are not allowed.

This is using the Python port of Pluto.ExpressionExplorer by Mikołaj Bochenski: https://github.com/lightning-notebook/engine and some Julia macro magic.

👀 Reading hidden code
39.1 ms
👀 Reading hidden code
pyr"""
y = x + x
"""
163 ms
👀 Reading hidden code
pyr"""
x = 2
"""
470 μs
👀 Reading hidden code
pyr"[x, y]"
20.6 ms
👀 Reading hidden code
pyr"""
def cool(x, y):
return 123123
"""
705 μs
123123
👀 Reading hidden code
pyr"cool(x, x)"
1.4 ms
👀 Reading hidden code
using PyCall
1.5 s
import Pkg
👀 Reading hidden code
166 μs
# Pkg.build("PyCall")
👀 Reading hidden code
8.0 μs
0.9092974268256817
begin
py"""
import math
math.sin
"""
py"math.sin"(2)
end
👀 Reading hidden code
18.8 ms
Error message

MethodError: objects of type Nothing are not callable

Stack trace

Here is what happened, the most recent locations are first:

  1. begin	py"""	import math
uhmmmmmm??!
begin
py"""
import math
math.sin"""(2)
end
👀 Reading hidden code
---
using PlutoUI
👀 Reading hidden code
146 ms
Expr
  head: Symbol macrocall
  args: Array{Any}((3,))
    1: Symbol @py_str
    2: LineNumberNode
      line: Int64 1
      file: Symbol /home/runner/work/disorganised-mess/disorganised-mess/reactive python.jl#==#6449e96f-19ce-4100-8b35-56113bc24b7d
    3: String "[1+1]"
Dump(:(py"[1+1]"))
👀 Reading hidden code
3.6 ms
Expr
  head: Symbol macrocall
  args: Array{Any}((3,))
    1: Symbol @py_str
    2: LineNumberNode
      line: Int64 1
      file: Symbol /home/runner/work/disorganised-mess/disorganised-mess/reactive python.jl#==#d9bc5dc1-67b6-47c6-9592-3b474ec451a0
    3: String "[1+1]\n"
Dump(:(py"""
[1+1]
"""))
👀 Reading hidden code
120 μs
rw = py_reads_writes("x=2")
👀 Reading hidden code
121 ms
rw[2] |> collect
👀 Reading hidden code
11.8 ms
quote
    #= /home/runner/work/disorganised-mess/disorganised-mess/reactive python.jl#==#5c432b4e-60e0-4288-8d5a-42e364ce8d16:8 =#
    begin
        #= /home/runner/work/disorganised-mess/disorganised-mess/reactive python.jl#==#5c432b4e-60e0-4288-8d5a-42e364ce8d16:12 =#
        let s = var"py#var_b"
            #= /home/runner/work/disorganised-mess/disorganised-mess/reactive python.jl#==#5c432b4e-60e0-4288-8d5a-42e364ce8d16:13 =#
            begin
                #= /home/runner/work/disorganised-mess/disorganised-mess/reactive python.jl#==#b9903477-5070-43eb-82a9-8b750f79c8fa:25 =#
                var"#471#m" = Main.workspace#4.pynamespace(Main.workspace#4.Main)
                #= /home/runner/work/disorganised-mess/disorganised-mess/reactive python.jl#==#b9903477-5070-43eb-82a9-8b750f79c8fa:26 =#
                begin
                    var"#471#m"["__julia_localvar_25_1"] = Main.workspace#4.PyObject(s)
                end
                #= /home/runner/work/disorganised-mess/disorganised-mess/reactive python.jl#==#b9903477-5070-43eb-82a9-8b750f79c8fa:27 =#
                var"#472#ret" = (PyAny)(Main.workspace#4.pyeval_(Base.string("b = __julia_localvar_25_1\n"), var"#471#m", var"#471#m", 257, "/home/runner/work/disorganised-mess/disorganised-mess/reactive python.jl#==#b9903477-5070-43eb-82a9-8b750f79c8fa"))
                #= /home/runner/work/disorganised-mess/disorganised-mess/reactive python.jl#==#b9903477-5070-43eb-82a9-8b750f79c8fa:28 =#
                nothing
                #= /home/runner/work/disorganised-mess/disorganised-mess/reactive python.jl#==#b9903477-5070-43eb-82a9-8b750f79c8fa:29 =#
                var"#472#ret"
            end
        end
    end
    #= /home/runner/work/disorganised-mess/disorganised-mess/reactive python.jl#==#5c432b4e-60e0-4288-8d5a-42e364ce8d16:19 =#
    var"#470#output" = begin
            #= /home/runner/work/disorganised-mess/disorganised-mess/reactive python.jl#==#b9903477-5070-43eb-82a9-8b750f79c8fa:25 =#
            var"#473#m" = Main.workspace#4.pynamespace(Main.workspace#4.Main)
            #= /home/runner/work/disorganised-mess/disorganised-mess/reactive python.jl#==#b9903477-5070-43eb-82a9-8b750f79c8fa:26 =#
            begin
            end
            #= /home/runner/work/disorganised-mess/disorganised-mess/reactive python.jl#==#b9903477-5070-43eb-82a9-8b750f79c8fa:27 =#
            var"#474#ret" = (PyAny)(Main.workspace#4.pyeval_((Main.workspace#5.Base).string("x = 123\ny = b\n"), var"#473#m", var"#473#m", 257, "/home/runner/work/disorganised-mess/disorganised-mess/reactive python.jl#==#b9903477-5070-43eb-82a9-8b750f79c8fa"))
            #= /home/runner/work/disorganised-mess/disorganised-mess/reactive python.jl#==#b9903477-5070-43eb-82a9-8b750f79c8fa:28 =#
            nothing
            #= /home/runner/work/disorganised-mess/disorganised-mess/reactive python.jl#==#b9903477-5070-43eb-82a9-8b750f79c8fa:29 =#
            var"#474#ret"
        end
    #= /home/runner/work/disorganised-mess/disorganised-mess/reactive python.jl#==#5c432b4e-60e0-4288-8d5a-42e364ce8d16:23 =#
    begin
        #= /home/runner/work/disorganised-mess/disorganised-mess/reactive python.jl#==#5c432b4e-60e0-4288-8d5a-42e364ce8d16:27 =#
        var"py#var_x" = begin
                #= /home/runner/work/disorganised-mess/disorganised-mess/reactive python.jl#==#b9903477-5070-43eb-82a9-8b750f79c8fa:25 =#
                var"#475#m" = Main.workspace#4.pynamespace(Main.workspace#4.Main)
                #= /home/runner/work/disorganised-mess/disorganised-mess/reactive python.jl#==#b9903477-5070-43eb-82a9-8b750f79c8fa:26 =#
                begin
                end
                #= /home/runner/work/disorganised-mess/disorganised-mess/reactive python.jl#==#b9903477-5070-43eb-82a9-8b750f79c8fa:27 =#
                var"#476#ret" = (PyAny)(Main.workspace#4.pyeval_(Base.string("x"), var"#475#m", var"#475#m", 258, "/home/runner/work/disorganised-mess/disorganised-mess/reactive python.jl#==#b9903477-5070-43eb-82a9-8b750f79c8fa"))
                #= /home/runner/work/disorganised-mess/disorganised-mess/reactive python.jl#==#b9903477-5070-43eb-82a9-8b750f79c8fa:28 =#
                begin
                end
                #= /home/runner/work/disorganised-mess/disorganised-mess/reactive python.jl#==#b9903477-5070-43eb-82a9-8b750f79c8fa:29 =#
                var"#476#ret"
            end
    end
    begin
        #= /home/runner/work/disorganised-mess/disorganised-mess/reactive python.jl#==#5c432b4e-60e0-4288-8d5a-42e364ce8d16:27 =#
        var"py#var_y" = begin
                #= /home/runner/work/disorganised-mess/disorganised-mess/reactive python.jl#==#b9903477-5070-43eb-82a9-8b750f79c8fa:25 =#
                var"#477#m" = Main.workspace#4.pynamespace(Main.workspace#4.Main)
                #= /home/runner/work/disorganised-mess/disorganised-mess/reactive python.jl#==#b9903477-5070-43eb-82a9-8b750f79c8fa:26 =#
                begin
                end
                #= /home/runner/work/disorganised-mess/disorganised-mess/reactive python.jl#==#b9903477-5070-43eb-82a9-8b750f79c8fa:27 =#
                var"#478#ret" = (PyAny)(Main.workspace#4.pyeval_(Base.string("y"), var"#477#m", var"#477#m", 258, "/home/runner/work/disorganised-mess/disorganised-mess/reactive python.jl#==#b9903477-5070-43eb-82a9-8b750f79c8fa"))
                #= /home/runner/work/disorganised-mess/disorganised-mess/reactive python.jl#==#b9903477-5070-43eb-82a9-8b750f79c8fa:28 =#
                begin
                end
                #= /home/runner/work/disorganised-mess/disorganised-mess/reactive python.jl#==#b9903477-5070-43eb-82a9-8b750f79c8fa:29 =#
                var"#478#ret"
            end
    end
    #= /home/runner/work/disorganised-mess/disorganised-mess/reactive python.jl#==#5c432b4e-60e0-4288-8d5a-42e364ce8d16:31 =#
    var"#470#output"
end
@macroexpand pyr"""
x = 123
y = b
"""
👀 Reading hidden code
725 μs
3
begin
custom_py"""
x = 3
"""

custom_py"x"
end
👀 Reading hidden code
28.3 ms
begin
rand(), try
var"#py_var_x"
catch end
end
👀 Reading hidden code
6.4 ms
123
123
👀 Reading hidden code
8.0 μs
1123124
pyr"f(1)"
👀 Reading hidden code
548 μs
pyr"""
def f(x):
return x + 1123123
"""
👀 Reading hidden code
632 μs
Error message

UndefVarError: @pyr_str not defined

Stack trace

Here is what happened, the most recent locations are first:

  1. from :0
  2. #macroexpand#51
  3. Base.remove_linenums!(Meta.macroexpand(@__MODULE__, :(pyr"""x = 123
Base.remove_linenums!(Meta.macroexpand(@__MODULE__, :(pyr"""
x = 123
y = b
"""); recursive=false))
👀 Reading hidden code
---
# string(Expr(:symbol, Symbol("#a")))
👀 Reading hidden code
8.6 μs
@pyr_str (macro with 1 method)
macro pyr_str(str)
var"@custom_py_str"
reads, writes = py_reads_writes(str)

quote
# if false
$([let
julia_s = Symbol("py#var_", py_s)
import_code = """$(py_s) = \$(s)\n"""
esc(quote
let s = $(julia_s) # for pluto to recognise
@custom_py_str($(import_code)) # load stored PyObject into python using that name
end
end)
end for py_s in reads]...)
# end
output = @custom_py_str($(str))
# if false

$([let
julia_s = Symbol("py#var_", py_s)
import_code = "$(py_s)"
esc(quote
$(julia_s) = @custom_py_str($(import_code)) # store PyObject as julia object, so that it can use Pluto's reactivity
end)
end for py_s in writes]...)
# end
output
end
end
👀 Reading hidden code
2.5 ms
@pyr_old_str (macro with 1 method)
macro pyr_old_str(str)
var"@custom_py_str"
reads, writes = py_reads_writes(str)

quote
if false
$([esc(:($(Symbol("#py", s)) = 1)) for s in writes]...)
$([esc(:($(Symbol("#py", s)))) for s in reads]...)
end
@custom_py_str($(str))
end
end
👀 Reading hidden code
1.9 ms
import PyCall: interpolate_pycode, Py_file_input, make_fname, Py_eval_input, pynamespace, pyeval_
👀 Reading hidden code
199 μs
2
custom_py"""
1 + 1"""
👀 Reading hidden code
285 ms
@custom_py_str (macro with 1 method)
macro custom_py_str(code, options...)
T = length(options) == 1 && 'o' in options[1] ? PyObject : PyAny
code, locals = interpolate_pycode(code)
input_type = '\n' in code ? Py_file_input : Py_eval_input
fname = make_fname(@__FILE__)
assignlocals = Expr(:block, [(isa(v,String) ?
:(m[$v] = PyObject($(esc(ex)))) :
nothing) for (v,ex) in locals]...)
code_expr = Expr(:call, esc(:(Base.string)))
i0 = firstindex(code)
for i in sort!(collect(filter(k -> isa(k,Integer), keys(locals))))
push!(code_expr.args, code[i0:prevind(code,i)], esc(locals[i]))
i0 = i
end
push!(code_expr.args, code[i0:lastindex(code)])
if input_type == Py_eval_input
removelocals = Expr(:block, [:(delete!(m, $v)) for v in keys(locals)]...)
else
# if we are evaluating multi-line input, then it is not
# safe to remove the local variables, because they might be referred
# to in Python function definitions etc. that will be called later.
removelocals = nothing
end
quote
m = pynamespace(Main)
$assignlocals
ret = $T(pyeval_($code_expr, m, m, $input_type, $fname))
$removelocals
ret
end
end
👀 Reading hidden code
4.7 ms
👀 Reading hidden code
17.0 ms
PyObject <function reads_writes at 0x7fd600fd87c0>
👀 Reading hidden code
432 ms